import math
from itertools import groupby

from odoo import fields, models, api
from odoo.tools import float_compare, float_round, float_is_zero


class SBFOPlaceOrderWizard(models.TransientModel):
    _name = "sb.fo.place.order.wiz"
    _description = "Place FO Order"

    fo_id = fields.Many2one('s.forecast.order', string="FO",
                            default=lambda self: self.env.context.get('fo_id', False))
    forecast_line_id = fields.Many2one('s.forecast.order.line', string="PN")
    customer_id = fields.Many2one('res.partner', string="Code",
                                  default=lambda self: self.env.context.get('customer_id', False))
    currency_id = fields.Many2one('res.currency', string="Currency", related="customer_id.property_product_pricelist.currency_id")
    po = fields.Char('PO')
    shipping_address = fields.Many2one('res.partner', string="Shipping Address", domain="[('parent_id', '=', customer_id)]")
    incoterm = fields.Many2one('account.incoterms', string="Incoterm", related="shipping_address.incoterm")
    # crm_id = fields.Many2one('crm.lead', related=)
    pn = fields.Many2one('customer.item.code', string="PN", related="forecast_line_id.market_forecast_product_name")
    product_id = fields.Many2one('product.product', string="MPN")
    pn_rev = fields.Char(related="pn.version", string="PN REV")
    pn_description = fields.Char('Description', related="pn.customer_item_desc")
    recent_price = fields.Float('Recent Price', related="pn.recent_price", digits=(16, 4))
    price = fields.Float('Unit Price', digits=(16, 4))
    o_price = fields.Float('Origin Price', digits=(16, 4))
    customer_order_date = fields.Date('Customer Order Date', default=fields.Date.today)
    order_qty = fields.Integer('Order Qty')
    ttl_amount = fields.Float('Total Amount', compute='_onchange_price_qty', digits=(16, 2))
    nre = fields.Float('NRE', digits=(16, 4))
    ext_charge = fields.Float('Ext Charge', digits=(16, 4))
    po_verify_value = fields.Boolean('PO Verify', compute='_po_verify_value', default=True)
    remark = fields.Text('Remark')
    qt = fields.Boolean(string="QTA")
    materiel_status = fields.Selection([
        ('S', 'NPI'),
        ('P', 'MP'),
        ('T', 'Test')
    ], string='Product Status', default=lambda self: self._get_materiel_status())
    special_order_type = fields.Selection([
        ('eq_only', 'EQ only'),
        ('free_cost', 'Free cost'),
        ('wb', 'WB')
    ], string="Special OT")
    po_line = fields.Char(string="PO Line", default="01")
    end_bool = fields.Boolean(string="End Customer", default=False)
    end_po = fields.Char(string="End PO")
    end_pn = fields.Char(string="End PN")
    end_pn_rev = fields.Char(string="End PN REV")

    def _get_materiel_status(self):
        pn = self.pn
        if pn:
            pn_id = self.env['customer.item.code'].search([('code', '=', pn)], limit=1)
            if pn_id:
                product_template = pn_id.product_id
                if product_template.materiel_status:
                    status = product_template.materiel_status
                    if status == 'Q':
                        return 'S'
                    else:
                        return status
        else:
            return False

    def _get_qta(self):
        pn = self.env.context.get('pn')
        if pn:
            pn_id = self.env['customer.item.code'].search([('code', '=', pn)], limit=1)
            if pn_id:
                product_template = pn_id.product_id
                if product_template.materiel_status:
                    status = product_template.materiel_status
                    if status == 'Q':
                        return True
                    else:
                        return False

    @api.onchange('po')
    @api.depends('po')
    def _po_verify_value(self):
        """搜索下单中存在同样的po"""
        if not self.po:
            self.po_verify_value = True
            return
        sale_order = self.env['sale.order'].search([('po', '=', self.po)])
        if sale_order:
            self.po_verify_value = False
        else:
            self.po_verify_value = True

    @api.onchange('price', 'order_qty')
    def _onchange_price_qty(self):
        self.ttl_amount = self.price * self.order_qty

    @api.onchange('forecast_line_id')
    def _onchange_fo_line(self):
        if self.forecast_line_id:
            product_tmpl_id = self.env['product.template'].search([
                ('serial_number', '=', self.forecast_line_id.engineering_confirmed_product_id.serial_number),
                ('s_disable_down_card', '=', False),
                ('categ_id.code', '=', 'CHP'),
            ], limit=1, order='id desc')
            if not product_tmpl_id:
                self.product_id = self.forecast_line_id.engineering_confirmed_product_id.product_variant_id.id,
            else:
                self.product_id = product_tmpl_id.product_variant_id.id
            self.order_qty = self.forecast_line_id.remain_qty
            self.price = self.forecast_line_id.price_unit

    def default_get(self, fields_list):
        result = super().default_get(fields_list)
        fo_id = self._context.get('default_fo_id')
        fo = self.env['s.forecast.order'].browse(fo_id)
        result['customer_id'] = fo.customer_id.id
        result['shipping_address'] = fo.customer_id.shipping_child_ids[0].id
        line = fo.forecast_order_line_ids.filtered(lambda l: l.remain_qty > 0)[:1]
        result['forecast_line_id'] = line.id if line else False
        return result

    def get_fo_line(self, so_id):
        """返回：所有剩余>0 且状态符合的 forecast 行，按剩余数量升序，最后一条可超分配"""
        self.ensure_one()
        so = self.env['sale.order'].browse(so_id)
        to_alloc = so.first_line.product_uom_qty
        pn = so.first_line.s_customer_item_code
        all_product_tmpl_ids = self.env['product.template'].search([
            ('serial_number', '=', so.product_id.serial_number),
            ('categ_id.code', '=', 'CHP')
        ])
        candidates = self.env['s.forecast.order.line'].search([
            ('engineering_confirmed_product_id', 'in', all_product_tmpl_ids.ids),
            ('forecast_order_id.state', 'not in', ['engineering_confirmed', 'marketing_confirmed', 'draft', 'cancel']),
            ('remain_qty', '>', 0)
        ], order='remain_qty asc')
        used_line_ids = self.env['s.forecast.order.line']
        if candidates:
            allocated = 0
            ForecastMOLine = self.env['s.forecast.mo.line']
            mo_lines = self.env['s.forecast.mo.line']
            for i, fo_line in enumerate(candidates):
                is_last = (i == len(candidates) - 1)
                can_take = (to_alloc - allocated) if is_last else min(fo_line.remain_qty, to_alloc - allocated)
                if can_take <= 0:
                    break
                mo_lines += ForecastMOLine.create({
                    'forecast_order_line_id': fo_line.id,
                    'sale_order_line_id': so.first_line.id,
                    'qty': can_take,
                })
                # 处理备料绑定
                product_lock_ids = self.env['s.product.lock'].search([
                    ('forecast_order_line_id', '=', fo_line.id),
                    ('active', 'in', (False, True)),
                    ('cancel', '=', False)
                ])
                if product_lock_ids:
                    line_ids = so.order_line.filtered(lambda x: x.product_id.detailed_type == 'product')
                    if can_take >= fo_line.remain_qty and line_ids:
                        sale_line = line_ids[0]
                        product_lock_ids.filtered(lambda x: not x.sale_order_line_id).update({'sale_id': so.id, 'sale_order_line_id': sale_line.id})
                        sale_line.s_pm_quantity += fo_line.pm_quantity
                    elif line_ids:
                        sale_line = line_ids[0]
                        ratio = can_take / fo_line.market_forecast_quantity
                        for product_id, g in groupby(product_lock_ids.sorted(lambda x: x.product_id.id), key=lambda x: x.product_id):
                            g_product_lock_ids = self.env['s.product.lock'].concat(*g)
                            total = sum(g_product_lock_ids.mapped('qty'))
                            need_qty = float_round(total * ratio, precision_rounding=product_id.uom_id.rounding)
                            for g_p in g_product_lock_ids.filtered(lambda x: not x.sale_order_line_id):
                                if float_is_zero(need_qty, precision_rounding=g_p.uom_id.rounding):
                                    continue
                                if float_compare(need_qty, g_p.qty, precision_rounding=g_p.uom_id.rounding) != -1:
                                    g_p.write({'sale_id': so.id, 'sale_order_line_id': sale_line.id})
                                    need_qty -= g_p.qty
                                else:
                                    new = g_p.copy({
                                        'sale_id': so.id,
                                        'sale_order_line_id': sale_line.id,
                                        'qty': need_qty,
                                        'purchase_id': g_p.purchase_id.id,
                                        'purchase_line_id': g_p.purchase_line_id.id,
                                        'purchase_requisition_id': g_p.purchase_requisition_id.id,
                                        'purchase_requisition_line_id': g_p.purchase_requisition_line_id.id,
                                        'prepare_qty': math.ceil(fo_line.pm_quantity * ratio)
                                    })
                                    g_p.qty -= need_qty
                                    need_qty = 0

                        sale_line.s_pm_quantity += (math.ceil(fo_line.pm_quantity * ratio))
                used_line_ids += fo_line
                allocated += can_take
                if allocated >= to_alloc:
                    break
        return used_line_ids if used_line_ids else None

    def confirmation(self):
        self.ensure_one()
        self.sudo()
        sale_order_obj = self.env['sale.order']
        sale_order_line_obj = self.env['sale.order.line']
        if self.materiel_status == 'T':
            tt = self.with_context(lang='en_US').env['sb.workflow.template'].search([('name', '=', 'For Test PCB Approval')], limit=1)
            ap_tp = tt.id if tt else False
        else:
            st = self.env['sb.workflow.template'].search([('name', '=', 'Standard MO Approval')], limit=1).id
            ap_tp = st if st else False
        name = self.env['ir.sequence'].next_by_code('sale.order')
        sale_order = sale_order_obj.create({
            'name': name + '-001',
            'partner_id': self.customer_id.id,
            'po': self.po,
            'incoterm': self.incoterm.id,
            'partner_shipping_id': self.shipping_address.id,
            'currency_id': self.currency_id.id,
            'sb_fo_line_id': self.forecast_line_id.id,
            'commitment_date': self.customer_order_date,
            'note': self.remark,
            'order_type': "repeat",
            'user_id': self.customer_id.user_id.id,
            'opportunity_id': self.product_id.crm_id.id,
            'sb_workflow_template_id': ap_tp,
        })
        if self.end_bool:
            sale_order.write({
                'end_po': self.end_po,
                'end_pn': self.end_pn,
                'end_pn_rev': self.end_pn_rev,
            })
        sale_order_line = sale_order_line_obj.create({
            'order_id': sale_order.id,
            'name': self.product_id.name,
            'product_template_id': self.product_id.product_tmpl_id.id,
            'product_id': self.product_id.id,
            'product_uom_qty': self.order_qty,
            'price_unit': self.price,
            'tax_id': self.customer_id.tax_ids.ids,
            's_customer_item_code': self.pn.id,
            's_qt': self.qt,
            's_materiel_status': self.materiel_status,
            'po_line': self.po_line,
            'po': self.po,
        })
        self.product_id.write({
            'materiel_status': 'Q' if self.materiel_status == 'S' and self.qt else self.materiel_status,
        })
        self.pn.write({
            'recent_price': self.price
        })
        if self.nre > 0:
            self.env['sale.order.line'].sudo().create({
                'order_id': sale_order.id,
                'product_template_id': self.env.ref('sb_sale.nre_server_product').id,
                'product_id': self.env.ref('sb_sale.nre_server_product').product_variant_id.id,
                'product_uom_qty': 1,
                'price_unit': self.nre,
                'name': 'NRE',
                'po': self.po,
                'tax_id': self.customer_id.tax_ids.ids
            })
        if self.ext_charge > 0:
            self.env['sale.order.line'].sudo().create({
                'order_id': sale_order.id,
                'product_template_id': self.env.ref('sb_sale.extra_charge_server_product').id,
                'product_id': self.env.ref('sb_sale.extra_charge_server_product').product_variant_id.id,
                'product_uom_qty': 1,
                'po': self.po,
                'price_unit': self.ext_charge,
                'name': 'Additional Charge',
                'tax_id': self.customer_id.tax_ids.ids
            })
        fo_line = self.get_fo_line(sale_order.id)
        if fo_line:
            sale_order.sb_fo_line_ids = fo_line.ids
        return {
            'name': 'Sale Order',
            'type': 'ir.actions.act_window',
            'res_model': 'sale.order',
            'view_mode': 'form',
            'res_id': sale_order.id,
            'target': 'current',
        }
